package ex55.mm.flow.impound.quote.common.kit;

import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class URLParser {
    public static URLParser create(String url) {
        try {
            return new URLParser(url);
        } catch (Exception x) {
            throw new IllegalArgumentException(x.getMessage(), x);
        }
    }


    private String host;

    private Integer port;

    private String protocol;

    // use LinkedHashMap to keep the order of items
    private LinkedHashMap<String, List<String>> params = new LinkedHashMap<String, List<String>>();

    private String path;

    private String userInfo;

    private String query;

    private String charset;

    private URLParser(String url) throws MalformedURLException, URISyntaxException {
        this(url, "utf-8");
    }

    /**
     * http://user:password@host:port/aaa/bbb;xxx=xxx?eee=ggg&fff=ddd&fff=lll
     *
     * @throws MalformedURLException
     */
    private URLParser(final String url, final String charset) throws MalformedURLException, URISyntaxException {
        checkNull(url, "url");
        if (charset != null && !Charset.isSupported(charset)) {
            throw new IllegalArgumentException("charset is not supported: " + charset);
        }
        final URL u;
        boolean hasDomain = true;
        String protocol;
        if (url.matches("\\w+[:][/][/].*")) {
            if (url.startsWith("http")) {
                u = new URL(url);
                protocol = u.getProtocol();
            } else {
                URI uri = new URI(url);
                protocol = uri.getScheme();
                u = new URL(url.replaceFirst(uri.getScheme(), "http"));
            }
        } else {
            hasDomain = false;
            u = new URL("http://dummy" + (url.startsWith("/") ? url : ("/" + url)));
            protocol = u.getProtocol();
        }

        this.charset = charset;
        if (hasDomain) {
            this.protocol = protocol;
            this.host = u.getHost();
            this.port = u.getPort();
            if (this.port == -1) {
                this.port = null;
            }
            this.path = u.getPath();
            this.userInfo = u.getUserInfo();
        } else {
            this.path = url.startsWith("/") ? u.getPath() : u.getPath().substring(1);
        }
        this.query = u.getQuery();
        this.params = parseQueryString(substringAfter(url, "?"));
    }


    public List<String> getRawParams(String name) {
        checkNull(name, "name");
        return this.params.get(name);
    }

    public String getRawParam(String name) {
        List<String> params = getRawParams(name);
        return params == null ? null : params.get(0);
    }

    public String getParam(String name) {
        String value = getRawParam(name);
        return value == null ? null : decode(value);
    }

    public List<String> getParams(String name) {
        List<String> rawParams = getRawParams(name);
        if (rawParams == null) {
            return null;
        }
        List<String> params = new ArrayList<>();
        for (String value : rawParams) {
            params.add(encode(value));
        }
        return params;
    }

    public Map<String, String> getSimple() {
        Map<String, String> map = new HashMap<>();
        for (String name : this.params.keySet()) {
            String value = this.params.get(name).get(0);
            map.put(name, encode(value));
        }
        return map;
    }

    public String getHost() {
        return host;
    }

    public Integer getPort() {
        return port;
    }

    public String getProtocol() {
        return protocol;
    }

    public String getFullPath() {
        return path;
    }

    public String getPath(int i) {
        int b = i + 1;
        String[] split = path.split("/", b + 2);
        if (split.length > b) {
            return split[b];
        } else {
            return null;
        }
    }

    public String getFullUserInfo() {
        return userInfo;
    }

    public String[] getUserInfo() {
        if (StringUtils.hasText(userInfo)) {
            String[] split = userInfo.split(":", 2);
            try {
                return new String[]{
                        !StringUtils.hasText(split[0]) ? "" : URLDecoder.decode(split[0], StandardCharsets.UTF_8.name()),
                        !StringUtils.hasText(split[1]) ? "" : URLDecoder.decode(split[1], StandardCharsets.UTF_8.name())
                };
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException(e);
            }
        }
        return null;
    }

    public String getCharset() {
        return charset;
    }

    public String getQuery() {
        return query;
    }

    public String createQueryString() {
        if (this.params.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (String name : this.params.keySet()) {
            List<String> values = this.params.get(name);
            for (String value : values) {
                if (sb.length() > 0) {
                    sb.append("&");
                }
                sb.append(name).append("=").append(encode(value));
            }
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (this.protocol != null) {
            sb.append(this.protocol).append("://");
        }
        if (this.userInfo != null) {
            sb.append(this.userInfo).append("@");
        }
        if (this.host != null) {
            sb.append(host);
        }
        if (this.port != null) {
            sb.append(":").append(this.port);
        }
        sb.append(this.path);
        String query = createQueryString();
        if (query.trim().length() > 0) {
            sb.append("?").append(query);
        }

        return sb.toString();
    }

    private String decode(String value) {
        checkNull(value, "value to decode");
        try {
            return charset == null ? value : URLDecoder.decode(value, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private String encode(String value) {
        checkNull(value, "value to encode");
        try {
            return charset == null ? value : URLEncoder.encode(value, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private static List<String> getOrCreate(Map<String, List<String>> map, String name) {
        checkNull(name, "name");
        return map.computeIfAbsent(name, k -> new ArrayList<>());
    }

    private static void checkNull(Object value, String fieldName) {
        if (value == null) {
            throw new IllegalArgumentException(fieldName + " should not be null");
        }
    }

    private static LinkedHashMap<String, List<String>> parseQueryString(String query) {
        LinkedHashMap<String, List<String>> params = new LinkedHashMap<>();
        if (isBlank(query)) {
            return params;
        }
        String[] items = query.split("&");
        for (String item : items) {
            String name = substringBefore(item);
            String value = substringAfter(item, "=");
            List<String> values = getOrCreate(params, name);
            values.add(value);
        }
        return params;
    }

    private static boolean isBlank(String str) {
        return str == null || str.trim().length() == 0;
    }

    private static String substringBefore(String str) {
        int index = str.indexOf("=");
        return index == -1 ? "" : str.substring(0, index);
    }

    private static String substringAfter(String str, String sep) {
        int index = str.indexOf(sep);
        return index == -1 ? "" : str.substring(index + 1);
    }

}
